package shardkv

//
// client code to talk to a sharded key/value service.
//
// the client first talks to the shardctrler to find out
// the assignment of shards (keys) to groups, and then
// talks to the group that holds the key's shard.
//

import (
	"6.824/labrpc"
	"fmt"
	"log"
	"os"
)
import "crypto/rand"
import "math/big"
import "6.824/shardctrler"
import "time"

//
// which shard is a key in?
// please use this function,
// and please do not change it.
//
func key2shard(key string) int {
	shard := 0
	if len(key) > 0 {
		shard = int(key[0])
	}
	shard %= shardctrler.NShards
	return shard
}

func nrand() int64 {
	max := big.NewInt(int64(1) << 62)
	bigx, _ := rand.Int(rand.Reader, max)
	x := bigx.Int64()
	return x
}

type Clerk struct {
	sm       *shardctrler.Clerk
	config   shardctrler.Config
	make_end func(string) *labrpc.ClientEnd
	// You will have to modify this struct.
	ClientId  int64
	CmdId     int64
	logger    *log.Logger
	leaderMap map[int]int
}

//
// the tester calls MakeClerk.
//
// ctrlers[] is needed to call shardctrler.MakeClerk().
//
// make_end(servername) turns a server name from a
// Config.Groups[gid][i] into a labrpc.ClientEnd on which you can
// send RPCs.
//
func MakeClerk(ctrlers []*labrpc.ClientEnd, make_end func(string) *labrpc.ClientEnd) *Clerk {
	ck := new(Clerk)
	ck.sm = shardctrler.MakeClerk(ctrlers)
	ck.make_end = make_end
	// You'll have to add code here.
	ck.ClientId = nrand()
	ck.leaderMap = make(map[int]int)

	shortId := ck.ClientId % 10000
	ck.logger = log.New(os.Stdout, fmt.Sprintf("clerk[%04d]: ", shortId), log.LstdFlags|log.Lmicroseconds)
	ck.logger.Printf("客户端初始化完成，配置版本: %d", ck.config.Num)
	ck.config = ck.sm.Query(-1)
	return ck
}

//
// fetch the current value for a key.
// returns "" if the key does not exist.
// keeps trying forever in the face of all other errors.
// You will have to modify this function.
//
func (ck *Clerk) Get(key string) string {
	ck.CmdId++
	args := GetArgs{}
	args.Key = key
	args.ClientId = ck.ClientId
	args.CmdId = ck.CmdId
	reqId := fmt.Sprintf("Get-%d-%d", ck.ClientId, ck.CmdId)
	for {
		shard := key2shard(key)
		gid := ck.config.Shards[shard]
		ck.logger.Printf("发起Get请求 %s，Key=%s，分片=%d，目标组=%d，当前已知领导者节点：%d",
			reqId, key, shard, gid, ck.leaderMap[gid])
		if servers, ok := ck.config.Groups[gid]; ok {
			if _, ok := ck.leaderMap[gid]; !ok {
				ck.leaderMap[gid] = 0
			}
			curLeader := ck.leaderMap[gid]
			start := curLeader
			for {
				srv := ck.make_end(servers[curLeader])
				var reply GetReply
				ok := srv.Call("ShardKV.Get", &args, &reply)
				if ok && (reply.Err == OK || reply.Err == ErrNoKey) {
					ck.leaderMap[gid] = curLeader
					return reply.Value
				}
				if ok && (reply.Err == ErrWrongGroup) {
					break
				} else { // ... not ok, or ErrWrongLeader
					curLeader = (curLeader + 1) % len(servers)
					if curLeader == start {
						break
					}
				}
			}
		}
		time.Sleep(100 * time.Millisecond)
		// ask controler for the latest configuration.
		ck.config = ck.sm.Query(-1)
	}

	return ""
}

//
// shared by Put and Append.
// You will have to modify this function.
//
func (ck *Clerk) PutAppend(key string, value string, op string) {
	args := PutAppendArgs{}
	args.Key = key
	args.Value = value
	args.Op = op

	ck.CmdId++
	args.ClientId = ck.ClientId
	args.CmdId = ck.CmdId
	for {
		shard := key2shard(key)
		gid := ck.config.Shards[shard]
		if servers, ok := ck.config.Groups[gid]; ok {
			if _, ok := ck.leaderMap[gid]; !ok {
				ck.leaderMap[gid] = 0
			}
			curLeader := ck.leaderMap[gid]
			start := curLeader
			for {
				srv := ck.make_end(servers[curLeader])
				var reply PutAppendReply
				ok := srv.Call("ShardKV.PutAppend", &args, &reply)
				if ok && (reply.Err == OK || reply.Err == ErrDuplicate) {
					return
				}
				if ok && reply.Err == ErrWrongGroup {
					break
				} else { // ... not ok, or ErrWrongLeader
					curLeader = (curLeader + 1) % len(servers)
					if curLeader == start {
						break
					}
				}
			}
		}
		time.Sleep(100 * time.Millisecond)
		// ask controler for the latest configuration.
		ck.config = ck.sm.Query(-1)
	}
}

func (ck *Clerk) Put(key string, value string) {
	ck.PutAppend(key, value, "Put")
}
func (ck *Clerk) Append(key string, value string) {
	ck.PutAppend(key, value, "Append")
}
